package cn.bingoo.lang;

import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Formatter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

public class Encoding {
    private static byte[] _base64en = new byte[65];
    private static byte[] _base64de = new byte[256];
    private static final byte B64INV = -128;

    public static String base64encode(byte[] code) {
        return base64encode(code, true);
    }

    public static void main(String[] args) {
        System.out.println("\u554A");
    }

    public static String base64encode(byte[] code, boolean crlf) {
        if (null == code) {
            return null;
        }
        if (0 == code.length) {
            return new String();
        }
        int len = code.length;
        int rem = len % 3;
        byte[] dst = new byte[4 + ((len - 1) / 3 << 2) + (crlf ? len / 57 : 0)];
        int column = 0;
        int spos = 0;
        int dpos = 0;
        len -= 2;

        while (spos < len) {
            byte b0 = code[spos];
            byte b1 = code[spos + 1];
            byte b2 = code[spos + 2];
            dst[dpos++] = _base64en[0x3F & b0 >>> 2];
            dst[dpos++] = _base64en[(0x30 & b0 << 4) + (0xF & b1 >>> 4)];
            dst[dpos++] = _base64en[(0x3C & b1 << 2) + (0x3 & b2 >>> 6)];
            dst[dpos++] = _base64en[0x3F & b2];
            spos += 3;
            column += 3;
            if (crlf && 57 == column) {
                dst[dpos++] = 10;
                column = 0;
            }
        }

        if (0 != rem) {
            byte b0 = code[spos];
            dst[dpos++] = _base64en[0x3F & b0 >>> 2];
            if (1 == rem)
            {
                dst[dpos++] = _base64en[0x30 & b0 << 4];
                dst[dpos++] = 61;
            }
            else {
                byte b1 = code[spos + 1];
                dst[dpos++] = _base64en[(0x30 & b0 << 4) + (0xF & b1 >>> 4)];
                dst[dpos++] = _base64en[0x3C & b1 << 2];
            }
            dst[dpos++] = 61;
        }

        return new String(dst);
    }

    public static byte[] base64decode(String coded) {
        return base64decode(coded, true);
    }

    public static byte[] base64decode(String coded, boolean crlf) {
        if (null == coded) {
            return null;
        }
        byte[] src = coded.getBytes();
        int len = src.length;
        int dlen = len - (crlf ? len / 77 : 0);
        dlen = (dlen >>> 2) + (dlen >>> 1);
        int rem = 0;
        if (61 == src[len - 1]) {
            rem++;
        }
        if (61 == src[len - 2]) {
            rem++;
        }
        dlen -= rem;
        byte[] dst = new byte[dlen];

        int pos = 0;
        int dpos = 0;
        int col = 0;

        len -= 4;

        while (pos < len) {
            byte b0 = _base64de[src[pos++]];
            byte b1 = _base64de[src[pos++]];
            byte b2 = _base64de[src[pos++]];
            byte b3 = _base64de[src[pos++]];

            if (-128 == b0 || -128 == b1 || -128 == b2 || -128 == b3) {
                throw new RuntimeException("Invalid character at or around position " + pos);
            }
            dst[dpos++] = (byte) (b0 << 2 | b1 >>> 4 & 0x3);
            dst[dpos++] = (byte) (b1 << 4 | b2 >>> 2 & 0xF);
            dst[dpos++] = (byte) (b2 << 6 | b3 & 0x3F);
            col += 4;

            if (crlf && 76 == col) {
                if (10 != src[pos++]) {
                    throw new RuntimeException("No linefeed found at position " + (pos - 1));
                }
                col = 0;
            }

        }

        byte b0 = _base64de[src[pos++]];
        byte b1 = _base64de[src[pos++]];
        byte b2 = _base64de[src[pos++]];
        byte b3 = _base64de[src[pos++]];
        if (-128 == b0 || -128 == b1 || -128 == b2 || -128 == b3) {
            throw new RuntimeException("Invalid character at or around position " + pos);
        }
        dst[dpos++] = (byte) (b0 << 2 | b1 >>> 4 & 0x3);
        if (2 == rem) {
            return dst;
        }
        dst[dpos++] = (byte) (b1 << 4 | b2 >>> 2 & 0xF);
        if (1 == rem) {
            return dst;
        }
        dst[dpos++] = (byte) (b2 << 6 | b3 & 0x3F);

        return dst;
    }

    public static String toHexString(byte[] b) {
        if (null == b) {
            return null;
        }
        int len = b.length;
        byte[] hex = new byte[len << 1];
        int i = 0;
        for (int j = 0; i < len; j += 2) {
            hex[j] = (byte) ((b[i] & 0xF0) >> 4);
            int tmp43_41 = j;
            byte[] tmp43_40 = hex;
            tmp43_40[tmp43_41] = (byte) (tmp43_40[tmp43_41] + (10 > hex[j] ? 48 : 87));
            hex[j + 1] = (byte) (b[i] & 0xF);
            int tmp82_81 = j + 1;
            byte[] tmp82_77 = hex;
            tmp82_77[tmp82_81] = (byte) (tmp82_77[tmp82_81] + (10 > hex[j + 1] ? 48 : 87));

            i++;
        }

        return new String(hex);
    }

    public static String hashMD5(String str)
    {
        return hashMD5(str.getBytes());
    }

    public static String hashMD5(byte[] bytes) {
        MessageDigest md = null;
        try {
            md = MessageDigest.getInstance("MD5");
            md.update(bytes);
            return toHexString(md.digest());
        }
        catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return "Exception!" + e.toString();
        }
    }

    public static String hashSHA(String str) {
        byte[] b = str.getBytes();
        MessageDigest md = null;
        try {
            md = MessageDigest.getInstance("SHA1");
            md.update(b);
            return toHexString(md.digest());
        }
        catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return "Exception!" + e.toString();
        }

    }

    public static synchronized String rot13(String input)
    {
        StringBuffer output = new StringBuffer();
        if (input != null) {
            for (int i = 0; i < input.length(); i++) {
                char inChar = input.charAt(i);
                if (((inChar >= 'A' ? 1 : 0) & (inChar <= 'Z' ? 1 : 0)) != 0) {
                    inChar = (char) (inChar + '\r');
                    if (inChar > 'Z') {
                        inChar = (char) (inChar - '\032');
                    }
                }
                if (((inChar >= 'a' ? 1 : 0) & (inChar <= 'z' ? 1 : 0)) != 0) {
                    inChar = (char) (inChar + '\r');
                    if (inChar > 'z') {
                        inChar = (char) (inChar - '\032');
                    }
                }
                output.append(inChar);
            }
        }
        return output.toString();
    }

    public static String urlDecode(String str)
    {
        try
        {
            return URLDecoder.decode(str, "utf-8");
        }
        catch (Exception e) {
            return "Exception!" + e.toString();
        }
    }

    public static String urlEncode(String str)
    {
        try
        {
            return URLEncoder.encode(str, "utf-8");
        }
        catch (Exception e) {
            return "Exception!" + e.toString();
        }
    }

    static
    {
        Arrays.fill(_base64de, B64INV);
        for (byte i = 0; i < 26; i = (byte) (i + 1)) {
            _base64en[i] = (byte) (65 + i);
            _base64en[26 + i] = (byte) (97 + i);
            _base64de[65 + i] = i;
            _base64de[97 + i] = (byte) (26 + i);
        }
        for (byte i = 48; i < 58; i = (byte) (i + 1)) {
            _base64en[4 + i] = i;
            _base64de[i] = (byte) (4 + i);
        }
        _base64en[62] = 43;
        _base64en[63] = 47;
        _base64en[64] = 61;
        _base64de[43] = 62;
        _base64de[47] = 63;
        _base64de[61] = 0;
    }

    public static String hashHMAC(String text, String password) {
        try {
            SecretKey secretKey = new SecretKeySpec(password.getBytes("UTF-8"), "HmacMD5");
            Mac mac = Mac.getInstance(secretKey.getAlgorithm());
            mac.init(secretKey);
            byte[] ret = mac.doFinal(text.getBytes("UTF-8"));
            return base64encode(ret);
        }
        catch (Exception e) {
            return "Exception!" + e.toString();
        }
    }

    private static final Pattern unicodePattern = Pattern.compile("\\\\u([0-9A-F]{4})", Pattern.CASE_INSENSITIVE);

    public static String unescapeUnicode(String s) {
        if (s == null || s.length() == 0) {
            return s;
        }
        String res = s;
        Matcher m = unicodePattern.matcher(res);
        while (m.find()) {
            res = res.replaceAll("\\" + m.group(0),
                    Character.toString((char) Integer.parseInt(m.group(1), 16)));
        }
        return res;
    }

    public static String escapeUnicode(String input) {
        if (input == null || input.length() == 0) {
            return input;
        }

        StringBuilder b = new StringBuilder(input.length() * 4);
        Formatter f = new Formatter(b);
        for (char c : input.toCharArray()) {
            f.format("\\u%04x", (int) c);
        }
        return b.toString();
    }
}
